/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.cf.code;

import com.android.dx.rop.cst.CstType;
import com.android.dx.rop.type.StdTypeList;
import com.android.dx.rop.type.TypeList;
import com.android.dx.util.FixedSizeList;
import com.android.dx.util.IntList;

/**
 * List of catch entries, that is, the elements of an "exception table," which
 * is part of a standard {@code Code} attribute.
 */
public final class ByteCatchList extends FixedSizeList {

	/** {@code non-null;} convenient zero-entry instance */
	public static final ByteCatchList EMPTY = new ByteCatchList(0);

	/**
	 * Constructs an instance.
	 * 
	 * @param count
	 *            the number of elements to be in the table
	 */
	public ByteCatchList(int count) {
		super(count);
	}

	/**
	 * Gets the total length of this structure in bytes, when included in a
	 * {@code Code} attribute. The returned value includes the two bytes for
	 * {@code exception_table_length}.
	 * 
	 * @return {@code >= 2;} the total length, in bytes
	 */
	public int byteLength() {
		return 2 + size() * 8;
	}

	/**
	 * Gets the indicated item.
	 * 
	 * @param n
	 *            {@code >= 0;} which item
	 * @return {@code null-ok;} the indicated item
	 */
	public Item get(int n) {
		return (Item) get0(n);
	}

	/**
	 * Sets the item at the given index.
	 * 
	 * @param n
	 *            {@code >= 0, < size();} which entry to set
	 * @param item
	 *            {@code non-null;} the item
	 */
	public void set(int n, Item item) {
		if (item == null) {
			throw new NullPointerException("item == null");
		}

		set0(n, item);
	}

	/**
	 * Sets the item at the given index.
	 * 
	 * @param n
	 *            {@code >= 0, < size();} which entry to set
	 * @param startPc
	 *            {@code >= 0;} the start pc (inclusive) of the handler's range
	 * @param endPc
	 *            {@code >= startPc;} the end pc (exclusive) of the handler's
	 *            range
	 * @param handlerPc
	 *            {@code >= 0;} the pc of the exception handler
	 * @param exceptionClass
	 *            {@code null-ok;} the exception class or {@code null} to catch
	 *            all exceptions with this handler
	 */
	public void set(int n, int startPc, int endPc, int handlerPc,
			CstType exceptionClass) {
		set0(n, new Item(startPc, endPc, handlerPc, exceptionClass));
	}

	/**
	 * Gets the list of items active at the given address. The result is
	 * automatically made immutable.
	 * 
	 * @param pc
	 *            which address
	 * @return {@code non-null;} list of exception handlers active at {@code pc}
	 */
	public ByteCatchList listFor(int pc) {
		int sz = size();
		Item[] resultArr = new Item[sz];
		int resultSz = 0;

		for (int i = 0; i < sz; i++) {
			Item one = get(i);
			if (one.covers(pc) && typeNotFound(one, resultArr, resultSz)) {
				resultArr[resultSz] = one;
				resultSz++;
			}
		}

		if (resultSz == 0) {
			return EMPTY;
		}

		ByteCatchList result = new ByteCatchList(resultSz);
		for (int i = 0; i < resultSz; i++) {
			result.set(i, resultArr[i]);
		}

		result.setImmutable();
		return result;
	}

	/**
	 * Helper method for {@link #listFor}, which tells whether a match is
	 * <i>not</i> found for the exception type of the given item in the given
	 * array. A match is considered to be either an exact type match or the
	 * class {@code Object} which represents a catch-all.
	 * 
	 * @param item
	 *            {@code non-null;} item with the exception type to look for
	 * @param arr
	 *            {@code non-null;} array to search in
	 * @param count
	 *            {@code non-null;} maximum number of elements in the array to
	 *            check
	 * @return {@code true} iff the exception type is <i>not</i> found
	 */
	private static boolean typeNotFound(Item item, Item[] arr, int count) {
		CstType type = item.getExceptionClass();

		for (int i = 0; i < count; i++) {
			CstType one = arr[i].getExceptionClass();
			if ((one == type) || (one == CstType.OBJECT)) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Returns a target list corresponding to this instance. The result is a
	 * list of all the exception handler addresses, with the given
	 * {@code noException} address appended if appropriate. The result is
	 * automatically made immutable.
	 * 
	 * @param noException
	 *            {@code >= -1;} the no-exception address to append, or
	 *            {@code -1} not to append anything
	 * @return {@code non-null;} list of exception targets, with
	 *         {@code noException} appended if necessary
	 */
	public IntList toTargetList(int noException) {
		if (noException < -1) {
			throw new IllegalArgumentException("noException < -1");
		}

		boolean hasDefault = (noException >= 0);
		int sz = size();

		if (sz == 0) {
			if (hasDefault) {
				/*
				 * The list is empty, but there is a no-exception address; so,
				 * the result is just that address.
				 */
				return IntList.makeImmutable(noException);
			}
			/*
			 * The list is empty and there isn't even a no-exception address.
			 */
			return IntList.EMPTY;
		}

		IntList result = new IntList(sz + (hasDefault ? 1 : 0));

		for (int i = 0; i < sz; i++) {
			result.add(get(i).getHandlerPc());
		}

		if (hasDefault) {
			result.add(noException);
		}

		result.setImmutable();
		return result;
	}

	/**
	 * Returns a rop-style catches list equivalent to this one.
	 * 
	 * @return {@code non-null;} the converted instance
	 */
	public TypeList toRopCatchList() {
		int sz = size();
		if (sz == 0) {
			return StdTypeList.EMPTY;
		}

		StdTypeList result = new StdTypeList(sz);

		for (int i = 0; i < sz; i++) {
			result.set(i, get(i).getExceptionClass().getClassType());
		}

		result.setImmutable();
		return result;
	}

	/**
	 * Item in an exception handler list.
	 */
	public static class Item {

		/** {@code >= 0;} the start pc (inclusive) of the handler's range */
		private final int startPc;

		/** {@code >= startPc;} the end pc (exclusive) of the handler's range */
		private final int endPc;

		/** {@code >= 0;} the pc of the exception handler */
		private final int handlerPc;

		/**
		 * {@code null-ok;} the exception class or {@code null} to catch all
		 * exceptions with this handler
		 */
		private final CstType exceptionClass;

		/**
		 * Constructs an instance.
		 * 
		 * @param startPc
		 *            {@code >= 0;} the start pc (inclusive) of the handler's
		 *            range
		 * @param endPc
		 *            {@code >= startPc;} the end pc (exclusive) of the
		 *            handler's range
		 * @param handlerPc
		 *            {@code >= 0;} the pc of the exception handler
		 * @param exceptionClass
		 *            {@code null-ok;} the exception class or {@code null} to
		 *            catch all exceptions with this handler
		 */
		public Item(int startPc, int endPc, int handlerPc,
				CstType exceptionClass) {
			if (startPc < 0) {
				throw new IllegalArgumentException("startPc < 0");
			}

			if (endPc < startPc) {
				throw new IllegalArgumentException("endPc < startPc");
			}

			if (handlerPc < 0) {
				throw new IllegalArgumentException("handlerPc < 0");
			}

			this.startPc = startPc;
			this.endPc = endPc;
			this.handlerPc = handlerPc;
			this.exceptionClass = exceptionClass;
		}

		/**
		 * Gets the start pc (inclusive) of the handler's range.
		 * 
		 * @return {@code >= 0;} the start pc (inclusive) of the handler's
		 *         range.
		 */
		public int getStartPc() {
			return startPc;
		}

		/**
		 * Gets the end pc (exclusive) of the handler's range.
		 * 
		 * @return {@code >= startPc;} the end pc (exclusive) of the handler's
		 *         range.
		 */
		public int getEndPc() {
			return endPc;
		}

		/**
		 * Gets the pc of the exception handler.
		 * 
		 * @return {@code >= 0;} the pc of the exception handler
		 */
		public int getHandlerPc() {
			return handlerPc;
		}

		/**
		 * Gets the class of exception handled.
		 * 
		 * @return {@code non-null;} the exception class; {@link CstType#OBJECT}
		 *         if this entry handles all possible exceptions
		 */
		public CstType getExceptionClass() {
			return (exceptionClass != null) ? exceptionClass : CstType.OBJECT;
		}

		/**
		 * Returns whether the given address is in the range of this item.
		 * 
		 * @param pc
		 *            the address
		 * @return {@code true} iff this item covers {@code pc}
		 */
		public boolean covers(int pc) {
			return (pc >= startPc) && (pc < endPc);
		}
	}
}
